#include "XmlServer.hh"
#include "XmlServerThread.hh"
#include "../Exception.hh"
#include <signal.h>
#include <sys/socket.h>

using namespace Geom;
using namespace Network;

/// \brief XmlServer példány
XmlServer* XmlServer::server;

/// \brief XmlServer példány lekérdezése/létrehozása.
XmlServer& XmlServer::instance() {
    if (!server)
        server = new XmlServer();

    return *server;
}

/**
 * \brief SIGINT kezelő függvény.
 *
 * A SIGINT-hez tartozó socket-be ír.
 */
void XmlServer::sigint_handler(int unused) {
    DONTCARE(unused);
    u1 x = 0;
    ::write(instance().sigintfd[0], &x, sizeof(x));
}

/**
 * \brief SIGTERM kezelő függvény.
 *
 * A SIGTERM-hez tartozó socket-be ír.
 */
void XmlServer::sigterm_handler(int unused) {
    DONTCARE(unused);
    u1 x = 0;
    ::write(instance().sigtermfd[0], &x, sizeof(x));
}

/**
 * \brief XmlServere default konstruktor.
 *
 * Inicializálja a tagváltozókat, és létrehozza a signal figyelő socketeket, a
 * QSocketNotifierrel együtt. Összeköti az activated signal-t a megfelelő signal
 * kezelő slottal.
 */
XmlServer::XmlServer():
    QTcpServer(),
    sslConfig(),
    sigintfd(),
    sigintNotifier(),
    sigtermNotifier()
{
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sigintfd) < 0)
        throw ServerInitError("socketpair sigintfd");

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sigtermfd) < 0)
        throw ServerInitError("socketpair sigtermfd");

    sigintNotifier = QSharedPointer< QSocketNotifier >(
      new QSocketNotifier(sigintfd[1], QSocketNotifier::Read)
    );
    sigtermNotifier = QSharedPointer< QSocketNotifier >(
      new QSocketNotifier(sigtermfd[1], QSocketNotifier::Read)
    );
    connect(
      sigintNotifier.data(), SIGNAL(activated(int)), this, SLOT(sigint(int))
    );
    connect(
      sigtermNotifier.data(), SIGNAL(activated(int)), this, SLOT(sigterm(int))
    );
}


/**
 * \brief Szerver elidítása
 * \param port     A TCP port, amit a szerver figyel.
 * \param keyPath  Az RSA titkos kulcs elérési útja.
 * \param certPath A szerver tanúsítvány elérési útja.
 * \param caPath   A CA tanúsítvány elérési útja.
 *
 * Regisztrálja a signal kezelő függvényeket, beállítja az sslConfig-ot, és
 * elindítja a szervert.
 */
void
XmlServer::start(u2 port, QString keyPath, QString certPath, QString caPath) {
    sslConfig = XmlSocket::ssl_config(keyPath, certPath, caPath);
    struct sigaction sInt;
    sInt.sa_handler = sigint_handler;
    sInt.sa_flags = SA_RESTART;

    if (sigemptyset(&sInt.sa_mask) < 0)
        throw ServerInitError("sigemptyset sInt");

    if (sigaction(SIGINT, &sInt, 0) < 0)
        throw ServerInitError("sigaction SIGINT");

    struct sigaction sTerm;
    sTerm.sa_handler = sigterm_handler;
    sTerm.sa_flags = SA_RESTART;

    if (sigemptyset(&sTerm.sa_mask) < 0)
        throw ServerInitError("sigemptyset sTerm");

    if (sigaction(SIGTERM, &sTerm, 0) < 0)
        throw ServerInitError("sigaction SIGTERM");

    if (!listen(QHostAddress::Any, port))
        throw ServerInitError("listen " + QString::number(port));

    qDebug("listening on port %i", port);
}

void XmlServer::stop() {
    close();
    XmlServerThread::close_all();
}

/**
 * \brief Bejövő kapcsolat esemény.
 * \param socketDescriptor A kapcsolathoz tartozó socket leíró.
 *
 * Akkor hívódik meg, ha új kapcsolat épül fel. Új szálat hoz létre, amely a
 * kapcsolat kiszolgálásával foglalkozik.
 */
void XmlServer::incomingConnection(int socketDescriptor) {
    XmlServerThread* thread =
      new XmlServerThread(socketDescriptor, sslConfig, this);
	connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
	thread->start();
}

/**
 * \brief SIGINT kezelő slot.
 * \param fd A SIGINT socket leíró.
 *
 * Elmenti a játék állapotát, és leállítja a szervert.
 */
void XmlServer::sigint(int fd) {
    DONTCARE(fd);
    u1 x;
    ::read(sigintfd[1], &x, sizeof(x));
    qDebug("SIGINT");
    instance().stop();
    // TODO mentés
    QCoreApplication::quit();
}


/**
 * \brief SIGTERM kezelő slot.
 * \param fd A SIGTERM socket leíró.
 *
 * Mentés nélkül leállítja a szervert.
 */
void XmlServer::sigterm(int fd) {
    DONTCARE(fd);
    u1 x;
    ::read(sigtermfd[1], &x, sizeof(x));
    qDebug("SIGTERM");
    QCoreApplication::quit();
}

